Repository navigation
feat: add library variable set list and view commands - #697
NickJosevski wants to merge 7 commits into
Conversation
NickJosevski
left a comment
There was a problem hiding this comment.
One correctness issue found (verified against the portal's route definitions); everything else looked good.
| } | ||
| } | ||
|
|
||
| webUrl := util.GenerateWebURL(opts.Host, set.SpaceID, fmt.Sprintf("library/variablesets/%s", set.GetID())) |
There was a problem hiding this comment.
The portal has no library/variablesets/{id} route, so this URL (printed in basic output, returned as WebUrl in JSON, and opened by --web) lands on the portal's not-found page. The variable set detail page lives at library/variables/{id}.
I verified this against the portal bundle of a current server: variableSetPage is defined with the route template /variables/{variableSetId} nested under /library, the legacy v3 redirects also use library/variables/..., and the string variablesets does not appear in any route template.
| webUrl := util.GenerateWebURL(opts.Host, set.SpaceID, fmt.Sprintf("library/variablesets/%s", set.GetID())) | |
| webUrl := util.GenerateWebURL(opts.Host, set.SpaceID, fmt.Sprintf("library/variables/%s", set.GetID())) |
The expected URLs in view_test.go (the basic and json cases) and the WebUrl assertion in test/integration/libraryvariableset_test.go encode the same wrong path and will need updating with it.
There was a problem hiding this comment.
Actioned in cf36ff8.
viewRun now builds the link as fmt.Sprintf("library/variables/%s", set.GetID()), so the basic-output line, the WebUrl field in JSON, and --web all point at the real variable set page. The same commit updates the three places that encoded the old path: the basic expectation and the WebUrl in the json expectation in pkg/cmd/libraryvariableset/view/view_test.go (TestLibraryVariableSetView, cases "outputFormat basic" and "outputFormat json"), and the assert.Contains on result.WebUrl in testLibraryVariableSetView in test/integration/libraryvariableset_test.go.
Kept the path inline rather than pulling out a constant: buildinformation/view/view.go is the only sibling that builds a library/... deep link and it does the same inline fmt.Sprintf, so a shared helper would be a one-caller abstraction.
I grepped the repo for variablesets afterwards. The remaining hits are all the API endpoint libraryvariablesets (the /api/Spaces-1/libraryvariablesets/all request expectations in the list/view tests, the root-resource link in test/testutil/fakeoctopusserver.go, and two comments about script modules sharing that endpoint) - those are correct as the API route and unrelated to the portal route.
go build ./... and go test ./... pass except test/integration, which fails to launch without OCTOPUS_TEST_URL/OCTOPUS_TEST_APIKEY; the integration assertion change is therefore unverified by a run here.
One thing I did not re-verify myself: the portal route claim. I have no portal bundle or live server in this environment, so I took the /library/variables/{variableSetId} route template from your evidence rather than confirming it independently.
Adds `octopus library-variable-set` with `list` and `view`. `view` fetches the set and its variables in one command and groups values that share a name, so a variable with several scoped values reads as one entry instead of several, in table, basic and json output. Read-only for now; create/update/delete of sets and variables is not included. Refs #346 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
list -f json emitted ID/VariableSetID; every other list command and view -f json use Id/VariableSetId. view -f json emitted "Variables": null when --filter matched nothing, and [] otherwise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Covers what MockHttpServer can't: real ScopeValues resolution, the server returning no value for a sensitive variable, script module exclusion, and the two json fixes above. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The portal has no `library/variablesets/{id}` route; the variable set
detail page lives at `library/variables/{id}`. Verified against a current
server's portal bundle: the route segment is
`library:createRouteSegment(routeTemplate`/library`, { ...
variableSetPage: createPageRoute(routeTemplate`/variables/{variableSetId}`) })`,
the legacy v3 redirect for a variable set is `/library/variables/new`, and
the deep-link map sends `libraryvariablesets` ids to `variableSetPage`.
The string `variablesets` appears in no route template.
Updates the basic/json expectations in view_test.go and the WebUrl
assertion in the integration test with it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
formatForBasic returned early on an empty group list, so `-f basic` dropped the "View this library variable set in Octopus Deploy" line for a set with no variables (or when --filter matched none) while every other basic view in the CLI always prints it. Adds a test for the filtered case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3d29d03 to
f0fb0cd
Compare
project variables list prints a bold NAME on every row alongside a dimmed ID column, and no table in the CLI blanks a cell to mean "same as above". Blanking read slightly better for a human but is a format nothing else uses, and it makes rows useless to grep and awk. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follows the table change; the integration assertions still expected the blanked continuation cells. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Refs #346
What this adds
A new top-level
octopus library-variable-setcommand (aliaseslibrary-variable-sets,lvs) with two read-only subcommands.library-variable-set list— lists the variable sets in the space, sorted by name, with--filter/-qfor substring matching. Script modules share thelibraryvariablesetsendpoint and are excluded (the existingsharedVariable.GetAllLibraryVariableSetshelper already filters onContentType == "Variables").library-variable-set view [<name> | <id>]— this is the part aimed at the issue. It resolves the set, fetches its variables, and groups every value stored under the same variable name into one entry, with the unscoped value first because it is the fallback the scoped ones override.--filter/-qnarrows to variables whose name contains a string;--web/-wopens the set in the browser. Prompts for the set in interactive mode, errors in--no-prompt.This addresses both pain points named in the issue:
/libraryvariablesets/and/variables/doesn't make much sense" — one command does both requests and stitches the result together.Scope IDs are resolved to display names via the variable set's
ScopeValues, falling back to the raw ID rather than erroring, so one unresolvable scope value can't break the whole view (this differs deliberately frompkg/cmd/project/variables/shared.ToScopeValues, which returns an error).Table output (
-f table, the default) prints one row per stored value, with the variable name on every row:-f jsonemits the set's own fields plusVariablesas an array of{Name, Values[]}, each value carryingId,Value,IsSensitive,IsScoped, aScopeobject using the API's own property names (Environment,Role,Machine,TenantTag, …) with resolved names, and a flatScopeSummarystring.-f basicprints a grouped text block. Sensitive values are masked as***.This also lights up the previously-unused
annotations.IsLibrarygroup, so the root help now has aLIBRARY COMMANDSsection.Deliberately left out
/api/{space}/variables/variableset-…is a read-modify-write of the whole variable set with optimistic concurrency onVersion; doing it safely (and prompting for scopes) is a much bigger surface than a first slice should carry, and it deserves its own design discussion — see the open question below.list, andviewwon't resolve one.viewreports onlyTemplateCount.pkg/question/selectors— resolution lives inpkg/cmd/libraryvariableset/shared, mirroringpkg/cmd/channel/shared.ResolveChannel. Worth promoting if a second command needs it.Follow-up surface, roughly in the order I'd do it:
create/deleteof a set (cheap, the SDK hasAdd/Update/DeleteByID);variable create/update/deletewithin a set, reusingpkg/cmd/project/variables/sharedscope flags; script-module support; templates.Test evidence
go build ./...— clean.go test ./pkg/...— pass, exit 0, no failures. New tests:pkg/cmd/libraryvariableset/shared/shared_test.go— grouping (including case-insensitive names and unscoped-first ordering), scope ID→name resolution, fallback to the raw ID for unknown scope values, tolerance of a missingScopeValueslookup, sensitive masking.pkg/cmd/libraryvariableset/list/list_test.go— table/json/basic, name filter, script modules excluded.pkg/cmd/libraryvariableset/view/view_test.go— required-in-automation-mode error, unknown-set error, interactive prompt, grouped table, basic, json.Test support changes:
NewLibraryVariableSet/NewVariableSetForLibraryVariableSetfixtures, and theLibraryVariables+Variableslinks added totestutil.NewRootResource()(they were missing, so the SDK couldn't build those service paths under test).Decisions — settled on existing convention
library-variable-set(aliaslvs). Every multi-word command in the CLI is a flat kebab-case noun —project-group,worker-pool,ephemeral-environment,build-information— and there is no namespace-parent command anywhere inNewCmdRoot.variable-setwould be ambiguous with project variable sets andoctopus project variable; alibraryparent would commit us to a namespace none of whose siblings (library certificate,library script-module,library step-template) exist yet. Abbreviated aliases have precedent:kubernetescarriesk8s. If we later want alibraryparent we can add it and alias.project variables listis the closest prior art:VariableAsJsonembeds the SDK variable and shadows its ID-bearingScopewith a name-bearingVariableScopeValues, so consumers get names only.tenant variables listdoes the same (OwnerName,Environmentas names). NoScopeIdsfield.view. The seven--*-scopeflags inpkg/cmd/project/variables/sharedexist on the write commands only;project variables listtakes just--projectand--git-ref. Read commands in this CLI filter on name, and-f jsonis the answer for anything more selective.IDcolumn.project variables listprints one row per variable with a boldNAMEand a dimmedIDcolumn, and no cell in any table in the CLI is blanked to imply "same as above". Blanking reads slightly better for a human, but it is a format nothing else in the CLI uses.Open question — for a maintainer, not prior art
Should variable editing live in the CLI at all? This PR is read-only, which already solves the issue's stated problem (understanding a variable set), but the issue author links the "update a variable set variable" docs page, so they clearly want to write too.
The write path is a whole-variable-set read-modify-write with a
Versionfor optimistic concurrency; a CLI that silently clobbers a concurrent edit would be worse than no CLI. It also needs sensitive-value handling — the server never returns them, so a naive round-trip would blank them.My suggestion is a middle ground next:
set-valuefor an existing variable identified by name + scope, a targeted read-modify-write with explicit conflict detection onVersion, before attempting general create/delete. I'd like a call on whether we're comfortable with the CLI writing variable sets at all before building it.🤖 Generated with Claude Code